﻿//-----------------------------------------------------------------------------------------------------------
// Wbfs Engine (WBFSe) Project by Omega Frost 
// http://wbfse.codeplex.com/
//
// WBFSe is Licensed under the terms of the 
// Microsoft Reciprocal License (Ms-RL)
//-----------------------------------------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using WBFSe.FormatApi;
using WBFSe.IO;
using WBFSe.Attributes;
using WBFSe.Builders;
using WBFSe.Builders.V1;
using WBFSe.IO.Files;
using WBFSe.Formats;

namespace WBFSe
{
    /// <summary>
    /// File loader
    /// </summary>
    static class WbfsLoader
    {
        // Variables

        private static Dictionary<String, IWbfsFormat> Formats
            = new Dictionary<String, IWbfsFormat>();

        // Methods

        /// <summary>
        /// Static constructor
        /// </summary>
        static WbfsLoader()
        {
            IWbfsFormat f;

            f = new PWbfsFormat();
            Formats.Add(f.Extension.ToLower(), f);

            f = new FWbfsFormat();
            Formats.Add(f.Extension.ToLower(), f);

            f = new ISOFormat();
            Formats.Add(f.Extension.ToLower(), f);
        }

        /// <summary>
        /// Retrieves the formats loaded by the engine
        /// </summary>
        /// <returns></returns>
        internal static IWbfsFormat[] GetLoadedFormats()
        {
            return Formats.Values.ToArray();
        }

        /// <summary>
        /// Loads a format from an assembly
        /// </summary>
        /// <param name="assemblyPath">Full path of the Assembly</param>
        /// <param name="formatClassName">Format descriptor name</param>
        /// <returns>True on success, false if a format with the same extension is already loaded</returns>
        internal static bool LoadFormatFromAssembly(string assemblyPath, string formatClassName)
        {
            Assembly assembly = Assembly.LoadFile(assemblyPath);
            IWbfsFormat format = (IWbfsFormat)assembly.
                CreateInstance(formatClassName);

            if (Formats.ContainsKey(format.Extension.ToLower()))
                return false;

            Formats.Add(format.Extension.ToLower(), format);
            return true;
        }

        /// <summary>
        /// Loads all the formats in an assembly
        /// </summary>
        /// <param name="assemblyPath">Full path of the Assembly</param>
        /// <returns>Number of loaded formats</returns>
        internal static int LoadFormatsFromAssembly(string assemblyPath)
        {
            Int32 loaded = 0;
            Assembly assembly = Assembly.LoadFile(assemblyPath);
            IEnumerable<IWbfsFormat> formats = from t in assembly.GetTypes()
                                               where t.GetInterfaces().Contains(typeof(IWbfsFormat))
                                               select (IWbfsFormat)t;

            foreach (IWbfsFormat format in formats)
            {
                if (Formats.ContainsKey(format.Extension.ToLower()))
                    continue;

                Formats.Add(format.Extension.ToLower(), format);
                loaded++;
            }

            return loaded;
        }

        /// <summary>
        /// Loads an especific file from a directory
        /// </summary>
        /// <param name="directory">Base path, \ terminated</param>
        /// <param name="filenames">list of the filenames in the directory</param>
        /// <param name="filename">The name of the file in the list to load</param>
        /// <param name="format">Optional format to load the file</param>
        /// <param name="file">Out file</param>
        /// <returns>True if there's a parser for the file and there's a game in it</returns>
        /// <exception cref="System.Exception">Format reader failure</exception>
        internal static bool LoadGame(string directory, string[] filenames, 
            string filename, IWbfsFormat format, out IWbfsFileInfo file)
        {
            String extension = Path.GetExtension(filename).ToLower();
            IWbfsDriveInfo drive = WbfsDevices.GetDrive(directory);

            if (drive == null) 
                throw new ArgumentException("Invalid path");

            if (!directory.EndsWith("\\"))
                directory += "\\";

            // Check drive-format compatibility
            if (drive.IsWbfs)
            {
                if (format == null)
                    format = Formats[".$wbfs"];

                if (format.Extension.ToLower() != ".$wbfs")
                    throw new FormatException("Format not supported for WBFS partitions");
            }
            else if (format == null)
            {
                // Empty is not a valid extension for normal partitions
                // when no format is provided
                if (String.IsNullOrEmpty(extension) || (extension == "."))
                {
                    file = null;
                    return false;
                }

                // Find the format, if it's not provided
                if (!Formats.TryGetValue(extension, out format))
                {
                    file = null;
                    return false;
                }
            }
        
            // Create the reader
            WbfsReader reader = format.InvokeReader();

            // Separate the container files
            reader.GetFiles(directory, filenames, filename);

            for (int i = 0; i < reader.Filenames.Count; i++)
            {
                for (int j = 0; j < filenames.Length; j++)
                {
                    if (filenames[j] == null)
                        continue;

                    if (String.Compare(reader.Filenames[i], filenames[j]) == 0)
                        filenames[j] = null;
                }
            }

            // Parse the container
            reader.ParseContainer();

            // For multi purpose containers, not
            // having a game inside is not a format error
            // and can be pretty common
            try
            {
                if (!reader.IsGame())
                {
                    file = null;
                    return false;
                }
            }
            catch
            {
                file = null;
                return false;
            }

            file = new WbfsFileV1(format, reader);
            return true;
        }

        /// <summary>
        /// Loads an especific file from a directory
        /// </summary>
        /// <param name="directory">Base path, \ terminated</param>
        /// <param name="filename">The name of the file in the list to load</param>
        /// <param name="format">Optional format to load the file</param>
        /// <param name="file">Out file</param>
        /// <returns>True if there's a parser for the file and there's a game in it</returns>
        /// <exception cref="System.Exception">Format reader failure</exception>
        internal static bool LoadGame(string directory, string filename, 
            IWbfsFormat format, out IWbfsFileInfo file)
        {
            IWbfsDriveInfo drive = WbfsDevices.GetDrive(directory);
            String[] filenames = drive.GetFilenames(directory);
            return LoadGame(directory, filenames, filename, format, out file);
        }

        /// <summary>
        /// Creates a temporary file
        /// </summary>
        /// <param name="directory">Destination folder</param>
        /// <param name="filename">Destination filename</param>
        /// <param name="source">Source game</param>
        /// <param name="format">Optional Source format</param>
        /// <param name="split">Optional Container split information</param>
        /// <exception cref="System.Exception">Format creator failure</exception>
        internal static IWbfsFileInfo CreateTemporaryFile(String directory, String filename, 
            IWbfsFileInfo source, IWbfsFormat format, SplitInfo split, BuildFlags flags)
        {
            IWbfsDriveInfo drive = WbfsDevices.GetDrive(directory);

            if ((drive == null) || !drive.IsReady)
                throw new ArgumentException("Invalid path");

            if (!directory.EndsWith("\\"))
                directory += "\\";

            // Check drive-format compatibility
            if (drive.IsWbfs)
            {
                if (format == null)
                    format = Formats[".$wbfs"];

                if (format.Extension.ToLower() != ".$wbfs")
                    throw new FormatException("Format not supported for WBFS partitions");
            }
            else
            {
                if (format == null)
                    format = source.Format;
            }

            // Game type compatibility
            if (!format.Platform.HasFlag((GamePlatform)source.Type))
                throw new FormatException("The format does not support the source game type");

            IWbfsStream stream = source.Open(FileAccess.Read, FileShare.Read);
            IDiscBuilder db;
            
            switch(source.Type)
            {
                case GameType.Wii: db = new BuilderV1(stream); break;
                default: throw new NotImplementedException("Game type not implemented");
            }

            if (!db.Build(flags))
                throw new FormatException("The disc format is invalid");

            stream.Close();

            WbfsCreator creator = format.InvokeCreator();
            creator.Initialize(directory, filename, db, split);

            if (creator.SectorSize != db.SectorSize)
            {
                UsageTable usage = new UsageTable(db.Usage,
                    creator.SectorSize / db.SectorSize);

                creator.ResetUsageTable(usage);
            }

            return new WbfsTempFile(source, format, creator, db);
        }

        /// <summary>
        /// Finishes the file creation
        /// </summary>
        /// <param name="temp">Temporary file</param>
        /// <param name="successful">The operation was successful</param>
        /// <returns>A new file, the same if it's not temporary or null if not successful</returns>
        /// <exception cref="System.Exception">Format reader failure</exception>
        internal static IWbfsFileInfo ConsolidateTemporaryFile(IWbfsFileInfo temp, bool successful)
        {
            if (temp.Temporary)
            {
                (temp as WbfsTempFile).Consolidate(successful);
                if (successful)
                {
                    WbfsLoader.LoadGame(temp.Path, temp.Filename,
                        temp.Format, out temp);
                }
                else
                {
                    temp = null;
                }
            }

            return temp;
        }
    }
}
